# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##
"""
Provide consoles (bar) to access commands and details using basic widgets.
  NPSStatusbar - details about current game.
  NPSAssistbar - interactive help for current game.
  NPSToolbar - shortcut to frequent menu actions.
  NPSMenuBar - various interactive commands for current game.
"""
__docformat__ = "restructuredtext en"
#n_ = lambda x: x
#_ = lambda x: x

import logging

LOGGER = logging.getLogger('PySolFC.Viewlib.Console')

from .widget import (Toolbar as _Toolbar, Statusbar as _Statusbar,
                     Menubar as _Menubar, _MENUPATH, Menu as _Menu,)
from ._menudata import (FileMenuMixin as _File, SelectMenuMixin as _Select,
                        EditMenuMixin as _Edit,)
from ._menugame import (GameMenuMixin as _Game, AssistMenuMixin as _Assist,
                        HelpMenuMixin as _Help,)
from ._menuopts import OptionMenuMixin as _Option
from ..settings import CONF_OPT, TITLE
from ..gamelib import state
from ..resourceng import TILE_DB


class _NPSMenubar(_File, _Select, _Edit, _Game, _Assist, _Option, _Help):
    """
    Menu system
    """

    def __init__(self):
        from sys import platform
        self.__options = {}
        self._meta_mkey = 'Ctrl-{}' if platform != 'darwin' else 'Cmd-{}'
        self._meta_bkey = 'Control-{}' if platform != 'darwin' else 'Command-{}'
        self.progress = None
        self.app = self.top = self.game = None
        self._cb_max = 0

    _get_state = lambda s, v: 'normal' if v else 'disabled'
    isconnected = lambda s: s.game is not None and s.top is not None

    def connect_app(self, app, progress=None):
        """
        connect_app(self, app[, progress:Progress=None) -> None
        """
        self.app = app
        self.top = app.top
        self.top.bind('<<update_menus>>',
                      lambda e, c=False: self.update_menu_state(clear=c))
        self.top.bind('<<reset_menus>>',
                      lambda e, c=True: self.update_menu_state(clear=c))
        self._create_options()

        # init columnbreak
        self._cb_max = int(self.top.winfo_screenheight()/23)

        # create menus
        menubar = _Menubar(self.top, name='menubar')
        self._create_menubar(menubar, progress)
        self.create_keybindings()
        if progress:
            progress.update(step=1)
        self.top.config(menu=menubar)

    def connect_game(self, game):
        """
        connect_game(game) -> None
        """
        self.game = game
        super(_NPSMenubar, self).connect_game(game)

    def _create_options(self):
        """
        Create variables for CONF_OPT and misc radio and check button commands.

        .. \_create_options()
        """
        import Tkinter
        # set option menu variables with conf opt value
        for key, type_ in [
               # automate menu
               ('autofaceup', 'Boolean'), ('autodrop', 'Boolean'),
               ('autodeal', 'Boolean'), ('quickplay', 'Boolean'),
               # support menu
               ('highlight_piles', 'Boolean'), ('highlight_cards', 'Boolean'),
               ('highlight_samerank', 'Boolean'), ('shuffle', 'Boolean'),
               ('highlight_not_matching', 'Boolean'), ('undo', 'Boolean'),
               ('mahjongg_show_removed', 'Boolean'), ('hint', 'Boolean'),
               ('shisen_show_hint', 'Boolean'), ('bookmarks', 'Boolean'),
               # card scale menu
               ('auto_scale', 'Boolean'), ('save_games_geometry', 'Boolean'),
               # animation menu
               ('animations', 'Int'), ('redeal_animation', 'Boolean'),
               ('win_animation', 'Boolean'),
               # cardview menu
               ('shrink_face_down', 'Boolean'), ('shadow', 'Boolean',),
               ('shade_filled_stacks', 'Boolean'), ('shade', 'Boolean',),
               ('negative_bottom', 'Boolean'),
               # statusbar menu
               ('statusbar', 'Boolean'), ('num_cards', 'Boolean'),
               ('helpbar', 'Boolean'),
               # toolbar menu
               ('toolbar', 'Int'), ('toolbar_compound', 'String'),
               # options menu
               ('sound', 'Boolean'), ('demo_logo', 'Boolean'),
               ('splashscreen', 'Boolean'),
               # mouse menu
               ('mouse_type', 'String'), ('mouse_undo', 'Boolean'),
               # themes menu
               ('tile_theme', 'String')]:
            self.__options[key] = getattr(Tkinter, '{}Var'.format(type_))()
            self._set_option(key, CONF_OPT[key])

        # set tabletile option variable with value from TILE_DB
        self.__options['tabletile'] = Tkinter.IntVar()
        self._set_option('tabletile', TILE_DB.choice())

        # set variables with their value set on game_connects.
        self.__options['gameid'] = Tkinter.IntVar()
        self.__options['comment'] = Tkinter.BooleanVar()
        self.__options['pause'] = Tkinter.BooleanVar()
        self.__options['cardback'] = Tkinter.IntVar()

        # set toolbar widget variables with conf opt value
        for widget in CONF_OPT['toolbar_vars'].keys():
            keyname = 'toolbar_{}'.format(widget)
            self.__options[keyname] = Tkinter.BooleanVar()
            self._set_option(keyname,
                                    CONF_OPT['toolbar_vars'].get(widget, False))

    def _get_option(self, key):
        """
        Return menu option value by key given.

        .. \_get_option(key:str) -> tkVar value
        """
        return self.__options[key].get()

    def _set_option(self, key, value):
        """
        Set menu option value by key given.

        .. \_set_option(key:str, value:*) -> None
        """
        return self.__options[key].set(value)

    def _get_path(self, value):
        """
        Returns menu path detail by value given.

        .. \_get_path(value:str) -> seq(Menu|None, int|None, Menu|None)
        """
        return _MENUPATH.get(value, [None] * 3)

    def _get_variable(self, key):
        """
        Returns menu option variable by key given.

        .. \_get_option(key:str) -> Variable
        """
        return self.__options[key]

    def _create_menubar(self, menubar, progress=None):
        """
        Create the menubar for all game's menus:
          - File menu
          - Select menu
          - Edit menu
          - Game menu
          - Assist menu
          - Options menu
          - Help menu

        .. \_create_menubar(menubar:MenuBar[, progress:Progress=None]) -> None
        """
        self._add_file_menu(_Menu(menubar, n_('&File')))
        if progress:
            progress.update(step=1)
        self._add_select_menu(_Menu(menubar, label=n_('&Select')))
        if progress:
            progress.update(step=3)
        self._add_edit_menu(_Menu(menubar, label=n_('&Edit')))
        self._add_game_menu(_Menu(menubar, label=n_('&Game')))
        if progress:
            progress.update(step=1)
        self._add_assist_menu(_Menu(menubar, label=n_('&Assist')))
        self._add_options_menu(_Menu(menubar, label=n_('&Options')))
        if progress:
            progress.update(step=1)
        self._add_help_menu(_Menu(menubar, label=n_('&Help')))
        if progress:
            progress.update(step=1)

    def create_keybindings(self):
        """
        Bind menu command to key press combo event. Call next method in order.
          - <escape> iconify window.

        .. create_keybindings() -> None
        """
        self._bind_keypress(None, 'Escape',
                            lambda e: self._iconify_game())

        ancestor = super(_NPSMenubar, self)
        if hasattr(ancestor, 'create_keybindings'):
            ancestor.create_keybindings()

    def _bind_keypress(self, modifier, key, func):
        """
        Bind modifier with upper and lower key with func.

        .. \_bind_keypress(modifier:str, key:str, func:func) -> None
        """
        modifier = modifier or '{}'
        seq = lambda m, k: '<{}>'.format(m.format('KeyPress-{}'.format(k)))
        self.top.bind(seq(modifier, key), func)
        if len(key) == 1 and key != key.upper():
            self.top.bind(seq(modifier, key.upper()), func)

#    @state.game_connect
#    def _key_press_handler(self, event):
#        """
#        Interrupts demo when key is pressed then handles or propagates event to
#        allow bound key combos to call their funcs.

#        .. \_key_press_handler(self, event) -> tkEventState
#        """
#        if event and event.char and self.game.is_demo:
#            self.game.demo_stop()  # Stop demo_mode
#            return 'break'

    def update_menu_state(self, clear=False):
        """
        Update menu item enable state. Call next method in order.

        .. update_menu_state([clear:bool=False]) -> None
        """
        ancestor = super(_NPSMenubar, self)
        if hasattr(ancestor, 'update_menu_state'):
            ancestor.update_menu_state()

    def set_state(self, value, path):
        """
        Update menu enable state for item at menu path with given state bool.

        ,, set_state(value:bool, path:str) -> None
        """
        LOGGER.debug('Setting menu state for %s', path)
        menu, index, = self._get_path('.menubar.{}'.format(path))[:2]
        menu.entryconfig(index, state=self._get_state(state))

    @state.cancel_drag(False)
    def _iconify_game(self):
        """
        Iconify program window.

        .. \_iconify_game() -> None
        """
        self.top.wm_iconify()


MENU_BAR = _NPSMenubar()


class _NPSStatusbar(_Statusbar):
    """
    Create a current stats status bar.
    """

    _regions = (('time', _('Playing time'), 10),
                ('moves', _('Moves/Total moves'), 10),
                ('gamenumber', _('Game number'), 26),
                ('stats', _('Games played: won/lost'), 12),)

    def connect_top(self, top, grip=False):
        """
        connect_top(top:TopLevel) -> None

        Connect status bar to application.
        """
        super(_NPSStatusbar, self).connect_top(top, grip=True)
        self.show(CONF_OPT['statusbar'])
        self.show_region('gamenumber', CONF_OPT['statusbar_game_number'])

    def _format_text(self, key, value):
        """
        _format_text(key:str, value:str|seq) -> str

        Format status bar label regions. Apply certain key values added format.
        """
        if key == 'moves':
            return '{}'.format(value)
        if key == 'stats' and isinstance(value, (tuple, list,)):
            return '{}: {[0]}'.format(sum(value), value)
        if key == 'time':
            return '{}'.format(value)
        return value or ''


class _NPSAssistbar(_Statusbar):
    """
    Create a assistance status bar.
    """

    def connect_top(self, top, grip=False):
        """
        connect_top(top:TopLevel) -> None

        Connect help bar to application.
        """
        super(_NPSAssistbar, self).connect_top(top, grip=True)
        self.config_region('info', justify='left', anchor='w')
        self.show(CONF_OPT['helpbar'])


class _NPSToolbar(_Toolbar):
    """
    Create a command tool bar.
    """
    _region_seq = ('new', 'restart', None, 'open', 'save', None, 'undo', 'redo',
                   'autodrop', 'shuffle', 'pause', None, 'statistics', 'rules',
                   None, 'quit', 'player',)

    _regions = {'buttons':
                {'new': ({'text': n_('New'),
                          'command': MENU_BAR._new_game},
                        _('New game'),),
                'restart': ({'text': n_('Restart'),
                             'command': MENU_BAR._restart_game},
                            _('Restart the\ncurrent game'),),
                'open': ({'text': n_('Open'),
                          'command': MENU_BAR._open_game_data},
                         _('Open a\nsaved game'),),
                'save': ({'text': n_('Save'),
                          'command': MENU_BAR._save_new_game_data},
                         _('Save game'),),
                'undo': ({'text': n_('Undo'),
                          'command': MENU_BAR._undo_game_move},
                         _('Undo last move'),),
                'redo': ({'text': n_('Redo'),
                          'command': MENU_BAR._redo_game_move},
                         _('Redo last move'),),
                'autodrop': ({'text': n_('Autodrop'),
                              'command': MENU_BAR._do_drop_move},
                             _('Auto drop cards'),),
                'shuffle': ({'text': n_('Shuffle'),
                             'command': MENU_BAR._do_shuffle_tiles},
                            _('Shuffle tiles'),),
                'pause': ({'text': n_('Pause'),
                           'command': MENU_BAR._do_pause_game,
                           'variable': lambda n='pause':
                                           MENU_BAR._get_variable(n)},
                          _('Pause game'),),
                'statistics': ({'text': n_('Statistics'),
                                'command': MENU_BAR._view_game_stat},
                               _('View statistics'),),
                'rules': ({'text': n_('Rules'),
                           'command': MENU_BAR._help_gameplay},
                           _('Rules for this game'),),
                'quit': ({'text': n_('Quit'),
                          'command': MENU_BAR._quit_game},
                         _('Quit {}').format(TITLE),)},
                'labels':
                {'player': ({'text': n_('Player')}, _('Player options'),)}}

    _format_text = lambda s, k, v: (v or _('Player')) if k == 'player' else v

    def connect_top(self, top):
        """
        Connect toolbar with TopLevel.

        Overide to show toolbar based on config option.

        .. connect_app(top:tkTopLevel) -> None
        """
        self._toolbar_var = CONF_OPT['toolbar_vars']
        super(_NPSToolbar, self).connect_top(top, MENU_BAR)
        self.show(side=CONF_OPT['toolbar'], force=True)

    def connect_game(self, game):
        """
        Connect toolbar with Game.

        Update shuffle button and player label.

        .. connect_game(game:Game) -> None
        """
        self.show_region('shuffle', CONF_OPT['toolbar_vars']['shuffle'] and
                                        hasattr(game, '_shuffle_tile'))
        self.update_region_text(player=CONF_OPT['player'])

    def _update_image(self, name, **kwargs):
        """
        Update region image.

        Overide to set image preference from config options.

        .. \_update_image(name:str[, compound:str=None, style:str='default',
             size:str='small']) -> None
        """
        ckey = lambda k: 'toolbar_{}'.format(k)

        for key in ['style', 'compound']:
            kwargs.setdefault(key, CONF_OPT[ckey(key)])

        kwargs.setdefault('size', ['small', 'large'][CONF_OPT[ckey('size')]])

        super(_NPSToolbar, self)._update_image(name, **kwargs)

    def _show_widget(self, widget, **kwargs):
        """
        Show region widget on toolbar.

        Override to set toolbar side from config option.

        .. \_show_widget(widget[, side:int=*optiobn*, **grid keywords**])
             -> None
        """
        kwargs.setdefault('side', CONF_OPT['toolbar'])
        return super(_NPSToolbar, self)._show_widget(widget, **kwargs)

    def show_region(self, name, show=None, force=False):
        """
        Show/Hide region button or label.

        Overide to call next method when given show changes. After update config
        option. Return success

        .. show_region(name:string[, show:bool=*option*]) -> bool
        """
        if not force and CONF_OPT['toolbar_vars'][name] == show:
            return False
        show = show or CONF_OPT['toolbar_vars'][name]
        super(_NPSToolbar, self).show_region(name, show)
        CONF_OPT['toolbar_vars'][name] = show
        return True

    def show(self, side=1, force=False, **kwargs):
        """
        Show toolbar on given side.

        Overide to call next method when given side changes. After update config
        option. Return success.

        .. show([side:int=**options**, force:True, resize:bool=True]) -> bool
        """
        if not force and CONF_OPT['toolbar'] == side:
            return False
        super(_NPSToolbar, self).show(side, side, **kwargs)
        CONF_OPT['toolbar'] = side
        return True

    def update_region_image(self, force=False, **kwargs):
        """
        Update button region images.

        Overide to call next method when given values changes. Update conig opt.

        .. update_region_image([force:bool=False, compound:str=**config**,
             style:str=**config**, size:str=**config**, ]) -> bool

        """
        ckey = lambda k: 'toolbar_{}'.format(k)
        keys = ['compound', 'style', 'size']
        # Check for config option changes
        if not force and any(CONF_OPT[ckey(k)] == kwargs.get(k) for k in keys):
            return False
        super(_NPSToolbar, self).update_region_image(**kwargs)

        for key in keys[:2]:
            kwargs.setdefault(key, CONF_OPT[ckey(k)])
            CONF_OPT[cget(key)] = kwargs[key]

        kwargs.setdefault('size', ['small', 'large'][CONF_OPT[ckey(keys[2])]])
        CONF_OPT[ckey(keys[2])] = bool('large' == kwargs[keys[2]])
        return True

STATUS_BAR = _NPSStatusbar(row=5, column=0, columnspan=3)
ASSIST_BAR = _NPSAssistbar(row=4, column=0, columnspan=3)
TOOL_BAR = _NPSToolbar()

def update_console(**kwargs):
    """
    Update status bar and tool bar texts.

    .. \update_console([keywords for status and assist consoles]) -> None

    *Parameters*:
      time: {str}
        show info in time region of status bar. Used by game for timer.
      moves: {tuple}
        show info in moves region of status bar. Used by game for moves.
      gamenumber: {str}
        show info in gamenumber region of status bar. Used by game for seed.
      stats: {str}
        show info in stats region of status bar. Used by game for win/loss.
      info: {str}
        show info in info region of status bar. Used by pile for num cards.
      assist: {str}
        show info in info region of assist bar. Used by pile for pile tip.
    """
    if 'assist' in kwargs:
        if CONF_OPT['helpbar']:
            ASSIST_BAR.update_region_text(info=info.get('assist'))
        del kwargs['assist']
    if 'info' in kwargs and not CONF_OPT['num_cards']:
        del kwargs['info']
    if CONF_OPT['statusbar']:
        STATUS_BAR.update_region_text(**kwargs)
    if CONF_OPT['toolbar']:
        TOOL_BAR.update_region_text(player=CONF_OPT['player'])